/*
 * JCrasherPlanner.java
 * 
 * Copyright 2005 Christoph Csallner and Yannis Smaragdakis.
 */
package edu.gatech.cc.cnc.params;

import static edu.gatech.cc.jcrasher.Assertions.notNull;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Hashtable;
import java.util.Map;
import java.util.Random;
import java.util.Vector;

import edu.gatech.cc.cnc.Main;
import edu.gatech.cc.jcrasher.Constants.PlanFilter;
import edu.gatech.cc.jcrasher.Constants.Visibility;
import edu.gatech.cc.jcrasher.planner.PlannerForNeeded;
import edu.gatech.cc.jcrasher.planner.TypeNeededNode;
import edu.gatech.cc.jcrasher.plans.expr.ConstructorCall;
import edu.gatech.cc.jcrasher.plans.expr.DotClass;
import edu.gatech.cc.jcrasher.plans.expr.Expression;
import edu.gatech.cc.jcrasher.plans.expr.MethodCall;
import edu.gatech.cc.jcrasher.plans.expr.literals.NullLiteral;

public class JCrasherPlanner implements PlannerForNeeded {
  
  protected static JCrasherPlanner theInstance;
  
  public static JCrasherPlanner instance() {
    if (theInstance==null)
      theInstance = new JCrasherPlanner();
    
    return theInstance;
  }
  
  /**
   * Cache plan space of every queried class
   */
  final protected Map<Class<?>, TypeNeededNode<?>> plans = 
    new Hashtable<Class<?>, TypeNeededNode<?>>();
  
	
	/* Class constants */
	protected final Random random = new Random();
	
	/* Class variables */
	protected Constructor<Object> new_Object = null;
	protected Constructor<Vector> new_Vector = null;
	protected Method Vector_addElement = null;
	
  /**
   * Constructor
   */
	protected JCrasherPlanner() {
		try {
			new_Object = Object.class.getDeclaredConstructor(new Class[0]);
			new_Vector = Vector.class.getDeclaredConstructor(new Class[0]);
			Vector_addElement = Vector.class.getDeclaredMethod("addElement", new Class[]{Object.class});
		}
		catch (Exception e) {e.printStackTrace();}
	}	
	
	/**
   * new Object()
   */
	protected ConstructorCall<Object> getDefaultObject() {
		notNull(new_Object);
		return new ConstructorCall<Object>(Vector.class, new_Object, new Expression[0]);
	}
	
	/**
   * (new Vector).addElement(new Object())
   */
	protected MethodCall<Vector> getDefaultVector() {
		notNull(new_Vector);

		ConstructorCall<Vector> receiver =
			new ConstructorCall<Vector>(Object.class, new_Vector, new Expression[0]);
		Expression[] params = new Expression[]{
				new ConstructorCall(Vector.class, new_Object, new Expression[0])					
		};
		
		return new MethodCall<Vector>(Vector.class, Vector_addElement, params, receiver);
	}
	
	
	/**
   * Object.class
   */
	protected Expression<Class> getDefaultClass() {
		return new DotClass();
	}		
	
	
  /**
   * @return default available for type.
   */
	public boolean hasDefault(Class<?> type) {
		return (Object.class.equals(type)
				|| Vector.class.equals(type) 
				|| Class.class.equals(type));
	}
  
	
	/**
	 * Get (non-JCrasher) default plan if preset.
	 */
	public <T> Expression<? extends T> getDefault(Class<T> type) {
		if (type.equals(Object.class))
      return (Expression<? extends T>) getDefaultObject();
    
		if (type.equals(Vector.class))
      return (Expression<? extends T>) getDefaultVector();
    
		if (type.equals(Class.class))
		  return (Expression<? extends T>) getDefaultClass();
    
		return null;		
	}
	
	
	
	/*
	 * Return a JCrasher-generated random plan for type. 
	 */
	public <T> Expression<? extends T> getRandomNonNull(
      Class<T> returnType,
      Class<?> testeeType)
  {
		notNull(returnType);
		
		/* Use the default in 80 percent of the cases we have one */
		Expression<? extends T> defaultPlan = getDefault(returnType); 
		if (defaultPlan!=null && random.nextInt(5)>0) {
			return defaultPlan;
		}
		 
    TypeNeededNode<T> typeNode = getPlanSpace(returnType);      
    BigInteger totalTestMethods = typeNode.getPlanSpaceSize(); 
		if (totalTestMethods.compareTo(BigInteger.ZERO) > 0) {
      /* JCrasher found some non-null plan. */
      BigDecimal random01 = new BigDecimal(random.nextDouble());
      BigInteger randomIndex = 
        random01.multiply(new BigDecimal(totalTestMethods)).toBigInteger();
      
			return typeNode.getPlan(randomIndex, testeeType);
		}

		return null;
	}
	
	
	
	/**
	 * @return a random plan (if all fails, return a NullPlan).
	 */
	public <T> Expression<? extends T> getRandom(
      Class<T> returnType,
      Class<?> testeeType) {
		notNull(testeeType);
		
    Expression<? extends T> res = getRandomNonNull(returnType, testeeType);
		if (res!=null)	//found a non-null plan
			return res;
		
		return new NullLiteral<T>(returnType, testeeType);
	}	
  
  
  /**
   * @return plan space of class T.
   */ 
  public <T> TypeNeededNode<T> getPlanSpace(Class<T> type) {
    if (plans.containsKey(type))  //cache hit.
      return (TypeNeededNode<T>) plans.get(type);
    
    TypeNeededNode<T> typeNode = new TypeNeededNode<T>(
        Main.TYPE_SPACE.getWrapper(type),
        Main.MAX_PLAN_RECURSION,
        PlanFilter.NON_NULL_TRANS,
        Visibility.GLOBAL);
    
    plans.put(type, typeNode);
    return typeNode;
  }  
}
